﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LPLib
{
    /// <summary>
    /// A pair of polynucleotides that may be of mixed type. This class provides the basis for pairwise alignment.
    /// </summary>
    /// <typeparam name="T">The nucleotide type of the first polynucleotide.</typeparam>
    /// <typeparam name="U">The nucleotide type of the second polynucleotide.</typeparam>
    public class PolymerPair<T, U>
    {
        /// <summary>
        /// The first of two polynucleotides.
        /// </summary>
        public Polymer<T> Polymer0;
        /// <summary>
        /// The second of two polynucleotides.
        /// </summary>
        public Polymer<U> Polymer1;
        ///// <summary>
        ///// The total length of the pairwise alignment between the two polynucleotides.
        ///// </summary>
        //public int Length;
        /// <summary>
        /// The alignment score of the pairwise alignment.
        /// </summary>
        public double AlignmentScore;
        /// <summary>
        /// The array of index values. The first index is the position in the alignment, the second index determines the polynucleotide to which the index refers (0 or 0).
        /// </summary>
        public int[][] Index;
        /// <summary>
        /// The alignment score by position.
        /// </summary>
        public double[] Score;
        /// <summary>
        /// Points to the location within the alignment of specified gene features.
        /// </summary>
        public Dictionary<GeneAnnotations, int> Features;

        private int nSegments;
        public int NSegments
        {
            get { return nSegments; }
        }

        private int[][] segmentStart;
        public int[][] SegmentStart
        {
            get { return segmentStart; }
        }

        private int[] segmentLength;
        public int[] SegmentLength
        {
            get { return segmentLength; }
        }

        /// <summary>
        /// Constructor. Sets AlignmentScore to NaN.
        /// </summary>
        public PolymerPair()
        {
            AlignmentScore = double.NaN;
            Features = new Dictionary<GeneAnnotations, int>();
        }

        /// <summary>
        /// Constructor. Creates a pair with specified polynucleotides.
        /// </summary>
        /// <param name="p0">The first polynucleotide in the pair.</param>
        /// <param name="p0">The second polynucleotide in the pair.</param>
        public PolymerPair(Polymer<T> p0, Polymer<U> p1)
        {
            Polymer0 = p0;
            Polymer1 = p1;
            AlignmentScore = double.NaN;
            Features = new Dictionary<GeneAnnotations, int>();
        }

        /// <summary>
        /// Creates a PolynucleotidePair from two Polynucleotides under the assumption that they are already aligned.
        /// </summary>
        /// <param name="p0">The first polynucleotide.</param>
        /// <param name="p0">The second polynucleotide.</param>
        /// <returns>A PolynucleotidePair with the specified polynucleotides and a completed Index.</returns>
        public static PolymerPair<T, U> PrealignedPair(Polymer<T> p0, Polymer<U> p1)
        {
            PolymerPair<T, U> pair = new PolymerPair<T, U>(p0, p1);
            pair.Index = new int[p0.Seq.Length][];
            for (int i = 0; i < pair.Index.Length; i++)
            {
                pair.Index[i] = new int[2] { i, i };
            }
            pair.Features = new Dictionary<GeneAnnotations, int>();
            return pair;
        }

        /// <summary>
        /// Counts the number of discontiguous gaps in the pairwise alignment of two Ig genes, up to the beginning of CDR3
        /// </summary>
        /// <returns>A list of integers describing the gaps found. Positive numbers give the lengths of insertions,
        /// negative numbers give the negative of the lengths of deletions.</returns>
        public static List<int> CountGaps(PolymerPair<T, U> pair)
        {
            List<int> gapCounts = new List<int>();

            // step through positions and count gaps by length of gap
            int indexLength = pair.Index.GetLength(0);

            bool start = false;
            bool deletion = false;
            bool insertion = false;
            int k = 0;
            start = true;
            for (; k < indexLength; k++)
            {
                if (pair.Index[k][0] >= 0 && pair.Index[k][1] >= 0)
                {
                    start = true;
                    break;
                }
            }
            if (!start)
                return gapCounts;

            int counter = 0;
            for (; k < pair.Index.Length; k++)
            {
                if (!deletion && !insertion)
                {
                    if (pair.Index[k][0] < 0)
                    {
                        // enter the deletion state
                        deletion = true;
                        counter = -1;
                    }
                    else if (pair.Index[k][1] < 0)
                    {
                        // enter the insertion state
                        insertion = true;
                        counter = 1;
                    }
                }
                else if (deletion) // the first index is negative
                {
                    if (pair.Index[k][0] < 0)
                    {
                        counter -= 1;
                    }
                    else if (pair.Index[k][1] < 0)
                    {
                        // issue a warning. This should not happen
                    }
                    else
                    {
                        // exit the deletion state
                        deletion = false;
                        gapCounts.Add(counter);
                    }
                }
                else if (insertion)
                {
                    if (pair.Index[k][1] < 0)
                    {
                        counter += 1;
                    }
                    else if (pair.Index[k][0] < 0)
                    {
                        // issue a warning
                    }
                    else
                    {
                        insertion = false;
                        gapCounts.Add(counter);
                    }
                }
            }
            return gapCounts;
        }

        /// <summary>
        /// Counts the number of discontiguous gaps in the pairwise alignment for this PolynucleotidPair.
        /// </summary>
        /// <returns>A list of integers describing the gaps found. Positive numbers give the lengths of insertions,
        /// negative numbers give the negative of the lengths of deletions.</returns>
        public List<int> CountGaps()
        {
            return CountGaps(this);
        }

        public static int[][] MergeIndices(int[][] index0, int[][] index1)
        {
            List<int[]> index = new List<int[]>();
            int k0 = 0;
            int k1 = 0;
            while (index0[k0][1] != index1[k1][1])
            {
                if (k0 >= index0.Length || k1 >= index1.Length)
                {
                    return null;
                }

                if (index0[k0][1] < index1[k1][1])
                {
                    k0++;
                }
                else
                {
                    k1++;
                }
            }

            while (k0 < index0.Length && k1 < index1.Length)
            {
                if (index0[k0][1] == index1[k1][1])
                {
                    index.Add(new int[] { index0[k0][0], index1[k1][0], index0[k0][1] });
                    k0++;
                    k1++;
                }
                else if (index0[k0][1] < 0)
                {
                    index.Add(new int[]{index0[k0][0], -1, index0[k0][1]});
                    k0++;
                }
                else
                {
                    index.Add(new int[] { -1, index1[k1][0], index1[k1][1] });
                }
            }
            return index.ToArray();
        }

        public static void TransferGaps(PolymerPair<T, U> pair, MonomerEncoding<T> encoding0, MonomerEncoding<U> encoding1)
        {
            T[] newSeq0 = new T[pair.Index.Length];
            U[] newSeq1 = new U[pair.Index.Length];

            for (int k = 0; k < pair.Index.Length; k++ )
            {
                if (pair.Index[k][0] > -1)
                {
                    newSeq0[k] = pair.Polymer0.Seq[pair.Index[k][0]];                                    
                }
                else
                {
                    newSeq0[k] = encoding0.Gap();                                  
                }
                pair.Index[k][0] = k;

                if (pair.Index[k][1] > -1)
                {
                    newSeq1[k] = pair.Polymer1.Seq[pair.Index[k][1]];
                }
                else
                {
                    newSeq1[k] = encoding1.Gap();
                }
                pair.Index[k][1] = k;
            }
            pair.Polymer0.Seq = newSeq0.ToArray();
            pair.Polymer1.Seq = newSeq1.ToArray();
        }

        public void AlignmentToSegments()
        {
            List<int[]> segmentStartAsList = new List<int[]>();
            segmentStartAsList.Add(new int[2]);
            List<int> segmentLengthAsList = new List<int>();
            segmentLengthAsList.Add(0);

            int segment = 0;
            if (Index[0][0] > -1)
            {
                // initial segment is present in polymer 0
                segmentStartAsList[0][0] = 0;
            }
            else
            {
                segmentStartAsList[0][0] = -1;
            }
            if (Index[0][1] > -1)
            {
                // initial segment is present in polymer 1
                segmentStartAsList[0][1] = 0;
            }
            else
            {
                segmentStartAsList[0][1] = -1;
            }

            for (int k = 1; k < Index.Length - 1; k++)
            {
                if ((Index[k][0] < 0 && Index[k + 1][0] > -1) || (Index[k][1] < 0 && Index[k + 1][1] > -1)
                    || (Index[k][0] > -1 && Index[k + 1][0] < 0) || (Index[k][1] > -1 && Index[k + 1][1] <0 ))
                {
                    segment++;
                    segmentStartAsList.Add(new int[2]);
                    segmentLengthAsList.Add(0);

                    if (Index[k + 1][0] < 0) // the new segment is absent in sequence 0...
                    {
                        segmentStartAsList[segment][0] = -1;   
                    }
                    else
                    {
                        segmentStartAsList[segment][0] = Index[k+1][0];
                    }

                    if (Index[k + 1][1] < 0) // the new segment is absent in sequence 1
                    {
                        segmentStartAsList[segment][1] = -1;
                    }
                    else
                    {
                        segmentStartAsList[segment][1] = Index[k+1][1];
                    }

                    if (segmentStartAsList[segment - 1][0] > -1)
                    {
                        segmentLengthAsList[segment - 1] = Index[k][0] + 1 - segmentStartAsList[segment - 1][0];
                    }
                    else
                    {
                        segmentLengthAsList[segment - 1] = Index[k][1] + 1 - segmentStartAsList[segment - 1][1];
                    }
                }
            }
            // finish up
            nSegments = segment + 1;
            if (segmentStartAsList[segment][0] > -1)
            {
                segmentLengthAsList[segment] = Index.Last()[0] + 1 - segmentStartAsList[segment][0];
            }
            else
            {
                segmentLengthAsList[segment] = Index.Last()[1] + 1 - segmentStartAsList[segment][1];
            }
            segmentLength = segmentLengthAsList.ToArray();
            segmentStart = segmentStartAsList.ToArray();
        }

    }

}
